//
//  iTunesLibrary.m
//  Amber
//
//  Created by Keith Duncan on 05/03/2008.
//  Copyright 2008 thirty-three. All rights reserved.
//

#import "iTunesLibrary.h"

#import "iTunesNode.h"

@interface iTunesLibrary ()
@property (readwrite, retain) AFSourceNode *rootNode;
@property (retain) id library;
@property (retain) iTunesNode *podcasts;
@end

@implementation iTunesLibrary

@synthesize rootNode=_rootNode;
@synthesize library=_library;
@synthesize podcasts=_podcasts;

- (void)dealloc {
	self.rootNode = nil;
	
	self.library = nil;
	self.podcasts = nil;
	
	[super dealloc];
}

#pragma mark -

enum {
	PlistParseStateUnknown = 0,
	PlistParseStatePlist = 1,
	PlistParseStateRoot = 2,
};

struct iTunesParserContext {
	NSInteger state;
	NSXMLElement *rootElement;
	
	NSXMLElement *nextInsertElement;
	BOOL returnNilForNextElement;
};
typedef struct iTunesParserContext iTunesParserContext;

void *CreateXMLStructureCallBack(CFXMLParserRef parser, CFXMLNodeRef node, void *info) {
	iTunesParserContext *context = info;
	
	NSXMLElement *newNode = nil;
	
	switch (CFXMLNodeGetTypeCode(node)) {
		case kCFXMLNodeTypeDocument:
		{
			newNode = [[[NSXMLElement alloc] initWithKind:NSXMLDocumentKind] autorelease];
			return newNode;
		}
		case kCFXMLNodeTypeElement:
		{
			if (context->nextInsertElement != nil) {
				newNode = context->nextInsertElement;
				context->nextInsertElement = nil;
				
				[context->rootElement addChild:newNode];
				
				newNode = (context->returnNilForNextElement ? nil : newNode);
				context->returnNilForNextElement = NO;
				
				return newNode;
			}
			
			NSString *nodeName = (id)CFXMLNodeGetString(node);
			newNode = [[[NSXMLElement alloc] initWithKind:NSXMLElementKind] autorelease];
			[newNode setName:(id)CFXMLNodeGetString(node)];
			
			if ([nodeName caseInsensitiveCompare:@"plist"] == NSOrderedSame) {
				context->state = PlistParseStatePlist;
			} else if ([nodeName caseInsensitiveCompare:@"dict"] == NSOrderedSame) {
				context->state = PlistParseStateRoot;
				context->rootElement = newNode;
			}
			
			return newNode;
		}
		case kCFXMLNodeTypeText:
		{
			NSString *nodeName = (id)CFXMLNodeGetString(node);
			if (context->state == PlistParseStateRoot && [nodeName caseInsensitiveCompare:@"tracks"] == NSOrderedSame) {
				context->nextInsertElement = [NSXMLElement elementWithName:@"dict"];
				context->returnNilForNextElement = YES;
			}
			
			newNode = (id)[NSXMLNode textWithStringValue:nodeName];
			return newNode;
		}
	}
	
	return newNode;
}

void AddChildCallBack(CFXMLParserRef parser, void *parent, void *child, void *info) {
	NSXMLElement *parentNode = parent, *childNode = child;
	[parentNode addChild:childNode];
}

void EndXMLStructureCallBack(CFXMLParserRef parser, void *xmlType, void *info) {
	
}

- (BOOL)loadAndReturnError:(NSError **)errorRef {
	if (![super loadAndReturnError:errorRef]) return NO;
	
	iTunesNode *sourceNodes[3];
	
	sourceNodes[0] = [[[iTunesNode alloc] initWithName:@"Library" representedObject:nil] autorelease];
	[sourceNodes[0] setTag:kiTunesLibrary];
	[sourceNodes[0] setPosition:0];
	
	sourceNodes[1] = [[[iTunesNode alloc] initWithName:@"Store" representedObject:nil] autorelease];
	[sourceNodes[1] setTag:kiTunesLibrary];
	[sourceNodes[1] setPosition:1];
	
	sourceNodes[2] = [[[iTunesNode alloc] initWithName:@"Playlists" representedObject:nil] autorelease];
	[sourceNodes[2] setTag:kiTunesLibrary];
	[sourceNodes[2] setPosition:2];
	
	AFSourceNode *rootNode = [[[AFSourceNode alloc] init] autorelease];
	[[rootNode mutableChildNodes] setArray:[NSArray arrayWithObjects:sourceNodes count:3]];
	
	@try {
		CFXMLParserCallBacks callbacks = {
			.createXMLStructure = CreateXMLStructureCallBack,
			.addChild = AddChildCallBack,
			.endXMLStructure = EndXMLStructureCallBack,
		};
		
		iTunesParserContext context = {0};
		
		CFXMLParserContext parserContext = {
			.info = &context,
		};
		
		NSData *libraryData = [NSData dataWithContentsOfMappedFile:[self.location path]];
		CFXMLParserRef parser = CFXMLParserCreate(kCFAllocatorDefault, (CFDataRef)libraryData, NULL, (CFXMLParserOptions)(kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace), kCFXMLNodeCurrentVersion, &callbacks, &parserContext);
		if (!CFXMLParserParse(parser)) [NSException raise:NSParseErrorException format:@"%s, the library couldn't be parsed", __PRETTY_FUNCTION__, nil];
		
		_library = CFXMLParserGetDocument(parser);
		CFStringRef errorString = NULL;
		_library = NSMakeCollectable(CFPropertyListCreateFromXMLData(kCFAllocatorDefault, (CFDataRef)[_library XMLData], 0, &errorString));
		
		NSArray *playlists = [self.library objectForKey:@"Playlists"];
		NSMutableSet *folders = [NSMutableSet set], *nestedItems = [NSMutableSet set];
		
		for (NSDictionary *playlist in playlists) {
			if ([playlist objectForKey:@"Visible"] != nil && ![[playlist objectForKey:@"Visible"] boolValue]) continue;
			
			AFSourceNode *parent = sourceNodes[0];
			iTunesNode *currentNode = [[[iTunesNode alloc] initWithName:[playlist objectForKey:@"Name"] representedObject:playlist] autorelease];
			
			NSInteger kind = NSNotFound;
			id kindObject = [playlist objectForKey:@"Distinguished Kind"];
			if (kindObject != nil) kind = [kindObject integerValue];
			
			switch (kind) {
				case /* Music */ 4:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:0];
					break;
				case /* Movies */ 2:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:1];
					break;
				case /* TV Shows */ 3:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:2];
					break;
				case /* Podcasts */ 10:
					self.podcasts = currentNode;
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:3];
					
					NSMutableDictionary *podcasts = [NSMutableDictionary dictionary];
					
					NSDictionary *tracks = [self.library objectForKey:@"Tracks"];
					NSArray *episodes = [playlist objectForKey:@"Playlist Items"];
					
					for (NSDictionary *currentEpisode in episodes) {
						NSDictionary *track = [tracks objectForKey:[[currentEpisode objectForKey:@"Track ID"] stringValue]];
						
						NSString *podcastTitle = [track objectForKey:@"Album"];
						if (podcastTitle == nil || [podcasts objectForKey:podcastTitle] != nil) continue;
						
						iTunesNode *currentPodcast = [[[iTunesNode alloc] initWithName:podcastTitle representedObject:currentEpisode] autorelease];
						currentPodcast.tag = kiTunesPodcast;
						
						[podcasts setValue:currentPodcast forKey:podcastTitle];
					}
					
					[[currentNode mutableChildNodes] addObjectsFromArray:[podcasts allValues]];
					
					break;
				case /* AudioBooks*/ 5:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:4];
					break;
				case /* Purchased */ 19:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:0];
					parent = sourceNodes[1];
					break;
				case /* Purchased on [...] */ 20:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:1];
					parent = sourceNodes[1];
					break;
				case /* Party Shuffle */ 22:
					[currentNode setTag:kiTunesLibrary];
					[currentNode setPosition:0];
					parent = sourceNodes[2];
					break;
				default:
					if ([playlist objectForKey:@"Smart Info"] != nil) {
						currentNode.tag = kiTunesSmartPlaylist;
					} else if ([[playlist objectForKey:@"Folder"] boolValue]) {				
						currentNode.tag = kiTunesFolder;
						
						[folders addObject:currentNode];
					}
					
					if ([playlist objectForKey:@"Parent Persistent ID"] != nil) {
						[nestedItems addObject:currentNode];
						continue;
					}
					
					parent = sourceNodes[2];
					break;
			}
			
			[[parent mutableChildNodes] addObject:currentNode];
		}
		
		for (iTunesNode *currentFolder in folders) {
			id currentFolderID = [currentFolder.representedObject objectForKey:@"Playlist Persistent ID"];
			
			for (iTunesNode *currentNode in nestedItems)
				if ([[currentNode.representedObject objectForKey:@"Parent Persistent ID"] isEqual:currentFolderID])
					[[currentFolder mutableChildNodes] addObject:currentNode];
		}
		
		NSArray *sortDescriptors = [NSArray arrayWithObjects:
									[[[NSSortDescriptor alloc] initWithKey:@"type" ascending:NO] autorelease],
									[[[NSSortDescriptor alloc] initWithKey:@"position" ascending:YES] autorelease],
									[[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease], nil];
		
		[rootNode sortWithSortDescriptors:sortDescriptors recursively:YES];
	}
	@catch (NSException *exception) {
		[[NSApplication sharedApplication] reportException:exception];
		
		if (errorRef != NULL) *errorRef = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadCorruptFileError userInfo:nil];
		return NO;
	}
	
	self.rootNode = rootNode;
	return YES;
}

- (BOOL)playlistIsPodcast:(NSString *)playlistName {
	for (iTunesNode *podcast in [self.podcasts childNodes])
		if ([[podcast name] isEqualToString:playlistName])
			return YES;
	
	return NO;
}

#pragma mark -

- (NSString *)localisedName {
	return @"iTunes";
}

- (NSURL *)expectedURL {
	return [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"/Music/iTunes/iTunes Music Library.xml"]];
}

- (NSArray *)additionalSearchURLs {
	NSDictionary *iAppPreferences = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.iApps"];
	NSArray *paths = [iAppPreferences objectForKey:@"iTunesRecentDatabasePaths"];
	
	NSMutableArray *locations = [NSMutableArray arrayWithCapacity:[paths count]];
	for (NSString *currentPath in paths) [locations addObject:[NSURL fileURLWithPath:currentPath]];
	
	return locations;
}

@end
